1 module unde.games.obj_loader; 2 3 import std.algorithm; 4 import std.array; 5 import std.conv; 6 import std.exception; 7 import std.path; 8 import std.stdio; 9 10 import std.string; 11 struct MtlMaterial 12 { 13 string name; 14 string map_diffuse; 15 string map_bump; 16 float[3] ambient; 17 float[3] diffuse; 18 float[3] specular; 19 float[3] emissive; 20 float specular_koef; 21 float optical_density; 22 float transparency; 23 int illum_model; 24 } 25 26 struct MtlFile 27 { 28 string filename; 29 MtlMaterial*[string] materials; 30 } 31 32 struct ObjIndex 33 { 34 int vert = -1; 35 int tex = -1; 36 int normal = -1; 37 } 38 39 struct ObjMesh 40 { 41 string material; 42 bool smooth; 43 ObjIndex*[][] faces; 44 } 45 46 struct ObjObject 47 { 48 string name; 49 float[3][] vertices; 50 float[2][] texcoords; 51 float[3][] normals; 52 53 ObjMesh*[] meshes; 54 } 55 56 struct ObjFile 57 { 58 string filename; 59 MtlFile *mtl; 60 ObjObject*[] objects; 61 } 62 63 private MtlFile*[string] mtl_files; 64 65 private float[3] get_3f(char[] str) 66 { 67 return str.splitter(" ").map!(a => a.to!(float)())().array[0..3]; 68 } 69 70 private float[2] get_2f(char[] str) 71 { 72 return str.splitter(" ").map!(a => a.to!(float)())().array[0..2]; 73 } 74 75 private int get_index(char[] str) 76 { 77 if (str == "") return -1; 78 else return str.to!(int); 79 } 80 81 private ObjIndex* get_indices(char[] str, int[3] offsets) 82 { 83 int[] numbers = str.splitter("/").map!(a => a.get_index())().array; 84 if (numbers.length < 1) numbers ~= -1; 85 if (numbers.length < 2) numbers ~= -1; 86 if (numbers.length < 3) numbers ~= -1; 87 numbers[] -= offsets[]; 88 return new ObjIndex(numbers[0]-1, numbers[1]-1, numbers[2]-1); 89 } 90 91 private ObjIndex*[] get_face(char[] str, int[3] offsets) 92 { 93 return str.splitter(" ").map!(a => a.get_indices(offsets))().array[0..$]; 94 } 95 96 MtlFile *load_mtlfile(string filename) 97 { 98 MtlFile *mtl = new MtlFile; 99 mtl.filename = filename; 100 101 string mat; 102 103 auto file = File(filename); 104 foreach (line; file.byLine()) 105 { 106 if (line == "" || line[0] == '#') 107 continue; 108 else if (line.startsWith("Ns ")) 109 { 110 mtl.materials[mat].specular_koef = line[3..$].to!(float); 111 } 112 else if (line.startsWith("Ka ")) 113 { 114 mtl.materials[mat].ambient = get_3f(line[3..$]); 115 } 116 else if (line.startsWith("Kd ")) 117 { 118 mtl.materials[mat].diffuse = get_3f(line[3..$]); 119 } 120 else if (line.startsWith("Ks ")) 121 { 122 mtl.materials[mat].specular = get_3f(line[3..$]); 123 } 124 else if (line.startsWith("Ke ")) 125 { 126 mtl.materials[mat].emissive = get_3f(line[3..$]); 127 } 128 else if (line.startsWith("Ni ")) 129 { 130 mtl.materials[mat].optical_density = line[3..$].to!(float); 131 } 132 else if (line.startsWith("d ")) 133 { 134 mtl.materials[mat].transparency = line[2..$].to!(float); 135 } 136 else if (line.startsWith("map_Kd ")) 137 { 138 mtl.materials[mat].map_diffuse = line[7..$].idup(); 139 } 140 else if (line.startsWith("map_d ")) 141 { 142 // Just ignore 143 } 144 else if (line.startsWith("illum ")) 145 { 146 mtl.materials[mat].illum_model = line[6..$].to!(int); 147 } 148 else if (line.startsWith("newmtl ")) 149 { 150 mat = line[7..$].idup(); 151 mtl.materials[mat] = new MtlMaterial; 152 } 153 else if (line.startsWith("map_Bump ")) 154 { 155 mtl.materials[mat].map_diffuse = line[9..$].idup(); 156 } 157 else 158 { 159 throw new Exception("Cannot parse mtl line: "~line.idup()); 160 } 161 } 162 163 return mtl; 164 } 165 166 ObjFile *load_objfile(string filename) 167 { 168 ObjFile *obj = new ObjFile; 169 obj.filename = filename; 170 171 int[3] offsets; 172 try 173 { 174 auto file = File(filename); 175 foreach (line; file.byLine()) 176 { 177 if (line == "" || line[0] == '#') 178 continue; 179 else if (line.startsWith("o ")) 180 { 181 if (obj.objects.length > 0) 182 { 183 offsets[0] += obj.objects[$-1].vertices.length; 184 offsets[1] += obj.objects[$-1].texcoords.length; 185 offsets[2] += obj.objects[$-1].normals.length; 186 } 187 188 obj.objects ~= new ObjObject; 189 obj.objects[$-1].name = line[2..$].idup(); 190 } 191 else if (line.startsWith("v ")) 192 { 193 obj.objects[$-1].vertices ~= get_3f(line[2..$]); 194 } 195 else if (line.startsWith("vt ")) 196 { 197 obj.objects[$-1].texcoords ~= get_2f(line[3..$]); 198 } 199 else if (line.startsWith("vn ")) 200 { 201 obj.objects[$-1].normals ~= get_3f(line[3..$]); 202 } 203 else if (line.startsWith("s ")) 204 { 205 string material; 206 bool n = true; 207 if (obj.objects[$-1].meshes.length > 0) 208 { 209 material = obj.objects[$-1].meshes[$-1].material; 210 n = obj.objects[$-1].meshes[$-1].faces.length > 0; 211 } 212 213 if (n) 214 { 215 obj.objects[$-1].meshes ~= new ObjMesh; 216 obj.objects[$-1].meshes[$-1].material = material; 217 } 218 219 obj.objects[$-1].meshes[$-1].smooth = (line[2..$] == "1"); 220 } 221 else if (line.startsWith("p ") || line.startsWith("l ") || line.startsWith("f ")) 222 { 223 obj.objects[$-1].meshes[$-1].faces ~= get_face(line[2..$], offsets); 224 } 225 else if (line.startsWith("usemtl ")) 226 { 227 obj.objects[$-1].meshes ~= new ObjMesh; 228 obj.objects[$-1].meshes[$-1].material = line[7..$].idup(); 229 } 230 else if (line.startsWith("mtllib ")) 231 { 232 string mtlfile = line[7..$].idup(); 233 234 if (!isAbsolute(mtlfile)) 235 { 236 mtlfile = chainPath(dirName(filename), mtlfile).array; 237 } 238 239 if (mtlfile !in mtl_files) 240 { 241 mtl_files[mtlfile] = load_mtlfile(mtlfile); 242 } 243 244 obj.mtl = mtl_files[mtlfile]; 245 } 246 else 247 { 248 throw new Exception("Cannot parse obj line: "~line.idup()); 249 } 250 } 251 } 252 catch (ErrnoException err) 253 { 254 writefln("%s", err); 255 } 256 257 return obj; 258 }